// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holders nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
package xyz.noark.asm;

/**
 * A {@link ClassVisitor} that generates a corresponding ClassFile structure, as
 * defined in the Java Virtual Machine Specification (JVMS). It can be used
 * alone, to generate a Java class "from scratch", or with one or more
 * {@link ClassReader} and adapter {@link ClassVisitor} to generate a modified
 * class from one or more existing Java classes.
 *
 * @see <a href=
 *      "https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html">JVMS
 *      4</a>
 * @author Eric Bruneton
 */
public class ClassWriter extends ClassVisitor {

	/**
	 * A flag to automatically compute the maximum stack size and the maximum
	 * number of local variables of methods. If this flag is set, then the
	 * arguments of the {@link MethodVisitor#visitMaxs} method of the
	 * {@link MethodVisitor} returned by the {@link #visitMethod} method will be
	 * ignored, and computed automatically from the signature and the bytecode
	 * of each method.
	 *
	 * <p>
	 * <b>Note:</b> for classes whose version is {@link Opcodes#V1_7} of more,
	 * this option requires valid stack map frames. The maximum stack size is
	 * then computed from these frames, and from the bytecode instructions in
	 * between. If stack map frames are not present or must be recomputed, used
	 * {@link #COMPUTE_FRAMES} instead.
	 *
	 * @see #ClassWriter(int)
	 */
	public static final int COMPUTE_MAXS = 1;

	/**
	 * A flag to automatically compute the stack map frames of methods from
	 * scratch. If this flag is set, then the calls to the
	 * {@link MethodVisitor#visitFrame} method are ignored, and the stack map
	 * frames are recomputed from the methods bytecode. The arguments of the
	 * {@link MethodVisitor#visitMaxs} method are also ignored and recomputed
	 * from the bytecode. In other words, {@link #COMPUTE_FRAMES} implies
	 * {@link #COMPUTE_MAXS}.
	 *
	 * @see #ClassWriter(int)
	 */
	public static final int COMPUTE_FRAMES = 2;

	// Note: fields are ordered as in the ClassFile structure, and those related
	// to attributes are
	// ordered as in Section 4.7 of the JVMS.

	/**
	 * The minor_version and major_version fields of the JVMS ClassFile
	 * structure. minor_version is stored in the 16 most significant bits, and
	 * major_version in the 16 least significant bits.
	 */
	private int version;

	/**
	 * The symbol table for this class (contains the constant_pool and the
	 * BootstrapMethods).
	 */
	private final SymbolTable symbolTable;

	/**
	 * The access_flags field of the JVMS ClassFile structure. This field can
	 * contain ASM specific access flags, such as
	 * {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the
	 * ClassFile structure.
	 */
	private int accessFlags;

	/** The this_class field of the JVMS ClassFile structure. */
	private int thisClass;

	/** The super_class field of the JVMS ClassFile structure. */
	private int superClass;

	/** The interface_count field of the JVMS ClassFile structure. */
	private int interfaceCount;

	/** The 'interfaces' array of the JVMS ClassFile structure. */
	private int[] interfaces;

	/**
	 * The fields of this class, stored in a linked list of {@link FieldWriter}
	 * linked via their {@link FieldWriter#fv} field. This field stores the
	 * first element of this list.
	 */
	private FieldWriter firstField;

	/**
	 * The fields of this class, stored in a linked list of {@link FieldWriter}
	 * linked via their {@link FieldWriter#fv} field. This field stores the last
	 * element of this list.
	 */
	private FieldWriter lastField;

	/**
	 * The methods of this class, stored in a linked list of
	 * {@link MethodWriter} linked via their {@link MethodWriter#mv} field. This
	 * field stores the first element of this list.
	 */
	private MethodWriter firstMethod;

	/**
	 * The methods of this class, stored in a linked list of
	 * {@link MethodWriter} linked via their {@link MethodWriter#mv} field. This
	 * field stores the last element of this list.
	 */
	private MethodWriter lastMethod;

	/** The number_of_classes field of the InnerClasses attribute, or 0. */
	private int numberOfInnerClasses;

	/**
	 * The 'classes' array of the InnerClasses attribute, or {@literal null}.
	 */
	private ByteVector innerClasses;

	/** The class_index field of the EnclosingMethod attribute, or 0. */
	private int enclosingClassIndex;

	/** The method_index field of the EnclosingMethod attribute. */
	private int enclosingMethodIndex;

	/** The signature_index field of the Signature attribute, or 0. */
	private int signatureIndex;

	/** The source_file_index field of the SourceFile attribute, or 0. */
	private int sourceFileIndex;

	/**
	 * The debug_extension field of the SourceDebugExtension attribute, or
	 * {@literal null}.
	 */
	private ByteVector debugExtension;

	/**
	 * The last runtime visible annotation of this class. The previous ones can
	 * be accessed with the {@link AnnotationWriter#previousAnnotation} field.
	 * May be {@literal null}.
	 */
	private AnnotationWriter lastRuntimeVisibleAnnotation;

	/**
	 * The last runtime invisible annotation of this class. The previous ones
	 * can be accessed with the {@link AnnotationWriter#previousAnnotation}
	 * field. May be {@literal null}.
	 */
	private AnnotationWriter lastRuntimeInvisibleAnnotation;

	/**
	 * The last runtime visible type annotation of this class. The previous ones
	 * can be accessed with the {@link AnnotationWriter#previousAnnotation}
	 * field. May be {@literal null}.
	 */
	private AnnotationWriter lastRuntimeVisibleTypeAnnotation;

	/**
	 * The last runtime invisible type annotation of this class. The previous
	 * ones can be accessed with the {@link AnnotationWriter#previousAnnotation}
	 * field. May be {@literal null}.
	 */
	private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;

	/** The Module attribute of this class, or {@literal null}. */
	private ModuleWriter moduleWriter;

	/** The host_class_index field of the NestHost attribute, or 0. */
	private int nestHostClassIndex;

	/** The number_of_classes field of the NestMembers attribute, or 0. */
	private int numberOfNestMemberClasses;

	/** The 'classes' array of the NestMembers attribute, or {@literal null}. */
	private ByteVector nestMemberClasses;

	/**
	 * The first non standard attribute of this class. The next ones can be
	 * accessed with the {@link Attribute#nextAttribute} field. May be
	 * {@literal null}.
	 *
	 * <p>
	 * <b>WARNING</b>: this list stores the attributes in the <i>reverse</i>
	 * order of their visit. firstAttribute is actually the last attribute
	 * visited in {@link #visitAttribute}. The {@link #toByteArray} method
	 * writes the attributes in the order defined by this list, i.e. in the
	 * reverse order specified by the user.
	 */
	private Attribute firstAttribute;

	/**
	 * Indicates what must be automatically computed in {@link MethodWriter}.
	 * Must be one of {@link MethodWriter#COMPUTE_NOTHING},
	 * {@link MethodWriter#COMPUTE_MAX_STACK_AND_LOCAL},
	 * {@link MethodWriter#COMPUTE_INSERTED_FRAMES}, or
	 * {@link MethodWriter#COMPUTE_ALL_FRAMES}.
	 */
	private int compute;

	// -----------------------------------------------------------------------------------------------
	// Constructor
	// -----------------------------------------------------------------------------------------------

	/**
	 * Constructs a new {@link ClassWriter} object.
	 *
	 * @param flags option flags that can be used to modify the default behavior
	 *            of this class. Must be zero or more of {@link #COMPUTE_MAXS}
	 *            and {@link #COMPUTE_FRAMES}.
	 */
	public ClassWriter(final int flags) {
		this(null, flags);
	}

	/**
	 * Constructs a new {@link ClassWriter} object and enables optimizations for
	 * "mostly add" bytecode transformations. These optimizations are the
	 * following:
	 *
	 * <ul>
	 * <li>The constant pool and bootstrap methods from the original class are
	 * copied as is in the new class, which saves time. New constant pool
	 * entries and new bootstrap methods will be added at the end if necessary,
	 * but unused constant pool entries or bootstrap methods <i>won't be
	 * removed</i>.
	 * <li>Methods that are not transformed are copied as is in the new class,
	 * directly from the original class bytecode (i.e. without emitting visit
	 * events for all the method instructions), which saves a <i>lot</i> of
	 * time. Untransformed methods are detected by the fact that the
	 * {@link ClassReader} receives {@link MethodVisitor} objects that come from
	 * a {@link ClassWriter} (and not from any other {@link ClassVisitor}
	 * instance).
	 * </ul>
	 *
	 * @param classReader the {@link ClassReader} used to read the original
	 *            class. It will be used to copy the entire constant pool and
	 *            bootstrap methods from the original class and also to copy
	 *            other fragments of original bytecode where applicable.
	 * @param flags option flags that can be used to modify the default behavior
	 *            of this class.Must be zero or more of {@link #COMPUTE_MAXS}
	 *            and {@link #COMPUTE_FRAMES}. <i>These option flags do not
	 *            affect methods that are copied as is in the new class. This
	 *            means that neither the maximum stack size nor the stack frames
	 *            will be computed for these methods</i>.
	 */
	public ClassWriter(final ClassReader classReader, final int flags) {
		super(Opcodes.ASM7);
		symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader);
		if ((flags & COMPUTE_FRAMES) != 0) {
			this.compute = MethodWriter.COMPUTE_ALL_FRAMES;
		} else if ((flags & COMPUTE_MAXS) != 0) {
			this.compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL;
		} else {
			this.compute = MethodWriter.COMPUTE_NOTHING;
		}
	}

	// -----------------------------------------------------------------------------------------------
	// Implementation of the ClassVisitor abstract class
	// -----------------------------------------------------------------------------------------------

	@Override
	public final void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) {
		this.version = version;
		this.accessFlags = access;
		this.thisClass = symbolTable.setMajorVersionAndClassName(version & 0xFFFF, name);
		if (signature != null) {
			this.signatureIndex = symbolTable.addConstantUtf8(signature);
		}
		this.superClass = superName == null ? 0 : symbolTable.addConstantClass(superName).index;
		if (interfaces != null && interfaces.length > 0) {
			interfaceCount = interfaces.length;
			this.interfaces = new int[interfaceCount];
			for (int i = 0; i < interfaceCount; ++i) {
				this.interfaces[i] = symbolTable.addConstantClass(interfaces[i]).index;
			}
		}
		if (compute == MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL && (version & 0xFFFF) >= Opcodes.V1_7) {
			compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES;
		}
	}

	@Override
	public final void visitSource(final String file, final String debug) {
		if (file != null) {
			sourceFileIndex = symbolTable.addConstantUtf8(file);
		}
		if (debug != null) {
			debugExtension = new ByteVector().encodeUtf8(debug, 0, Integer.MAX_VALUE);
		}
	}

	@Override
	public final ModuleVisitor visitModule(final String name, final int access, final String version) {
		return moduleWriter = new ModuleWriter(symbolTable, symbolTable.addConstantModule(name).index, access, version == null ? 0 : symbolTable.addConstantUtf8(version));
	}

	@Override
	public void visitNestHost(final String nestHost) {
		nestHostClassIndex = symbolTable.addConstantClass(nestHost).index;
	}

	@Override
	public final void visitOuterClass(final String owner, final String name, final String descriptor) {
		enclosingClassIndex = symbolTable.addConstantClass(owner).index;
		if (name != null && descriptor != null) {
			enclosingMethodIndex = symbolTable.addConstantNameAndType(name, descriptor);
		}
	}

	@Override
	public final AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
		if (visible) {
			return lastRuntimeVisibleAnnotation = AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation);
		} else {
			return lastRuntimeInvisibleAnnotation = AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation);
		}
	}

	@Override
	public final AnnotationVisitor visitTypeAnnotation(final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
		if (visible) {
			return lastRuntimeVisibleTypeAnnotation = AnnotationWriter.create(symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation);
		} else {
			return lastRuntimeInvisibleTypeAnnotation = AnnotationWriter.create(symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation);
		}
	}

	@Override
	public final void visitAttribute(final Attribute attribute) {
		// Store the attributes in the <i>reverse</i> order of their visit by
		// this method.
		attribute.nextAttribute = firstAttribute;
		firstAttribute = attribute;
	}

	@Override
	public void visitNestMember(final String nestMember) {
		if (nestMemberClasses == null) {
			nestMemberClasses = new ByteVector();
		}
		++numberOfNestMemberClasses;
		nestMemberClasses.putShort(symbolTable.addConstantClass(nestMember).index);
	}

	@Override
	public final void visitInnerClass(final String name, final String outerName, final String innerName, final int access) {
		if (innerClasses == null) {
			innerClasses = new ByteVector();
		}
		// Section 4.7.6 of the JVMS states "Every CONSTANT_Class_info entry in
		// the constant_pool table
		// which represents a class or interface C that is not a package member
		// must have exactly one
		// corresponding entry in the classes array". To avoid duplicates we
		// keep track in the info
		// field of the Symbol of each CONSTANT_Class_info entry C whether an
		// inner class entry has
		// already been added for C. If so, we store the index of this inner
		// class entry (plus one) in
		// the info field. This trick allows duplicate detection in O(1) time.
		Symbol nameSymbol = symbolTable.addConstantClass(name);
		if (nameSymbol.info == 0) {
			++numberOfInnerClasses;
			innerClasses.putShort(nameSymbol.index);
			innerClasses.putShort(outerName == null ? 0 : symbolTable.addConstantClass(outerName).index);
			innerClasses.putShort(innerName == null ? 0 : symbolTable.addConstantUtf8(innerName));
			innerClasses.putShort(access);
			nameSymbol.info = numberOfInnerClasses;
		}
		// Else, compare the inner classes entry nameSymbol.info - 1 with the
		// arguments of this method
		// and throw an exception if there is a difference?
	}

	@Override
	public final FieldVisitor visitField(final int access, final String name, final String descriptor, final String signature, final Object value) {
		FieldWriter fieldWriter = new FieldWriter(symbolTable, access, name, descriptor, signature, value);
		if (firstField == null) {
			firstField = fieldWriter;
		} else {
			lastField.fv = fieldWriter;
		}
		return lastField = fieldWriter;
	}

	@Override
	public final MethodVisitor visitMethod(final int access, final String name, final String descriptor, final String signature, final String[] exceptions) {
		MethodWriter methodWriter = new MethodWriter(symbolTable, access, name, descriptor, signature, exceptions, compute);
		if (firstMethod == null) {
			firstMethod = methodWriter;
		} else {
			lastMethod.mv = methodWriter;
		}
		return lastMethod = methodWriter;
	}

	@Override
	public final void visitEnd() {
		// Nothing to do.
	}

	// -----------------------------------------------------------------------------------------------
	// Other public methods
	// -----------------------------------------------------------------------------------------------

	/**
	 * Returns the content of the class file that was built by this ClassWriter.
	 *
	 * @return the binary content of the JVMS ClassFile structure that was built
	 *         by this ClassWriter.
	 * @throws ClassTooLargeException if the constant pool of the class is too
	 *             large.
	 * @throws MethodTooLargeException if the Code attribute of a method is too
	 *             large.
	 */
	public byte[] toByteArray() {
		// First step: compute the size in bytes of the ClassFile structure.
		// The magic field uses 4 bytes, 10 mandatory fields (minor_version,
		// major_version,
		// constant_pool_count, access_flags, this_class, super_class,
		// interfaces_count, fields_count,
		// methods_count and attributes_count) use 2 bytes each, and each
		// interface uses 2 bytes too.
		int size = 24 + 2 * interfaceCount;
		int fieldsCount = 0;
		FieldWriter fieldWriter = firstField;
		while (fieldWriter != null) {
			++fieldsCount;
			size += fieldWriter.computeFieldInfoSize();
			fieldWriter = (FieldWriter) fieldWriter.fv;
		}
		int methodsCount = 0;
		MethodWriter methodWriter = firstMethod;
		while (methodWriter != null) {
			++methodsCount;
			size += methodWriter.computeMethodInfoSize();
			methodWriter = (MethodWriter) methodWriter.mv;
		}
		// For ease of reference, we use here the same attribute order as in
		// Section 4.7 of the JVMS.
		int attributesCount = 0;
		if (innerClasses != null) {
			++attributesCount;
			size += 8 + innerClasses.length;
			symbolTable.addConstantUtf8(Constants.INNER_CLASSES);
		}
		if (enclosingClassIndex != 0) {
			++attributesCount;
			size += 10;
			symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD);
		}
		if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) {
			++attributesCount;
			size += 6;
			symbolTable.addConstantUtf8(Constants.SYNTHETIC);
		}
		if (signatureIndex != 0) {
			++attributesCount;
			size += 8;
			symbolTable.addConstantUtf8(Constants.SIGNATURE);
		}
		if (sourceFileIndex != 0) {
			++attributesCount;
			size += 8;
			symbolTable.addConstantUtf8(Constants.SOURCE_FILE);
		}
		if (debugExtension != null) {
			++attributesCount;
			size += 6 + debugExtension.length;
			symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION);
		}
		if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
			++attributesCount;
			size += 6;
			symbolTable.addConstantUtf8(Constants.DEPRECATED);
		}
		if (lastRuntimeVisibleAnnotation != null) {
			++attributesCount;
			size += lastRuntimeVisibleAnnotation.computeAnnotationsSize(Constants.RUNTIME_VISIBLE_ANNOTATIONS);
		}
		if (lastRuntimeInvisibleAnnotation != null) {
			++attributesCount;
			size += lastRuntimeInvisibleAnnotation.computeAnnotationsSize(Constants.RUNTIME_INVISIBLE_ANNOTATIONS);
		}
		if (lastRuntimeVisibleTypeAnnotation != null) {
			++attributesCount;
			size += lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
		}
		if (lastRuntimeInvisibleTypeAnnotation != null) {
			++attributesCount;
			size += lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
		}
		if (symbolTable.computeBootstrapMethodsSize() > 0) {
			++attributesCount;
			size += symbolTable.computeBootstrapMethodsSize();
		}
		if (moduleWriter != null) {
			attributesCount += moduleWriter.getAttributeCount();
			size += moduleWriter.computeAttributesSize();
		}
		if (nestHostClassIndex != 0) {
			++attributesCount;
			size += 8;
			symbolTable.addConstantUtf8(Constants.NEST_HOST);
		}
		if (nestMemberClasses != null) {
			++attributesCount;
			size += 8 + nestMemberClasses.length;
			symbolTable.addConstantUtf8(Constants.NEST_MEMBERS);
		}
		if (firstAttribute != null) {
			attributesCount += firstAttribute.getAttributeCount();
			size += firstAttribute.computeAttributesSize(symbolTable);
		}
		// IMPORTANT: this must be the last part of the ClassFile size
		// computation, because the previous
		// statements can add attribute names to the constant pool, thereby
		// changing its size!
		size += symbolTable.getConstantPoolLength();
		int constantPoolCount = symbolTable.getConstantPoolCount();
		if (constantPoolCount > 0xFFFF) {
			throw new ClassTooLargeException(symbolTable.getClassName(), constantPoolCount);
		}

		// Second step: allocate a ByteVector of the correct size (in order to
		// avoid any array copy in
		// dynamic resizes) and fill it with the ClassFile content.
		ByteVector result = new ByteVector(size);
		result.putInt(0xCAFEBABE).putInt(version);
		symbolTable.putConstantPool(result);
		int mask = (version & 0xFFFF) < Opcodes.V1_5 ? Opcodes.ACC_SYNTHETIC : 0;
		result.putShort(accessFlags & ~mask).putShort(thisClass).putShort(superClass);
		result.putShort(interfaceCount);
		for (int i = 0; i < interfaceCount; ++i) {
			result.putShort(interfaces[i]);
		}
		result.putShort(fieldsCount);
		fieldWriter = firstField;
		while (fieldWriter != null) {
			fieldWriter.putFieldInfo(result);
			fieldWriter = (FieldWriter) fieldWriter.fv;
		}
		result.putShort(methodsCount);
		boolean hasFrames = false;
		boolean hasAsmInstructions = false;
		methodWriter = firstMethod;
		while (methodWriter != null) {
			hasFrames |= methodWriter.hasFrames();
			hasAsmInstructions |= methodWriter.hasAsmInstructions();
			methodWriter.putMethodInfo(result);
			methodWriter = (MethodWriter) methodWriter.mv;
		}
		// For ease of reference, we use here the same attribute order as in
		// Section 4.7 of the JVMS.
		result.putShort(attributesCount);
		if (innerClasses != null) {
			result.putShort(symbolTable.addConstantUtf8(Constants.INNER_CLASSES)).putInt(innerClasses.length + 2).putShort(numberOfInnerClasses).putByteArray(innerClasses.data, 0, innerClasses.length);
		}
		if (enclosingClassIndex != 0) {
			result.putShort(symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD)).putInt(4).putShort(enclosingClassIndex).putShort(enclosingMethodIndex);
		}
		if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) {
			result.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0);
		}
		if (signatureIndex != 0) {
			result.putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE)).putInt(2).putShort(signatureIndex);
		}
		if (sourceFileIndex != 0) {
			result.putShort(symbolTable.addConstantUtf8(Constants.SOURCE_FILE)).putInt(2).putShort(sourceFileIndex);
		}
		if (debugExtension != null) {
			int length = debugExtension.length;
			result.putShort(symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION)).putInt(length).putByteArray(debugExtension.data, 0, length);
		}
		if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
			result.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0);
		}
		AnnotationWriter.putAnnotations(symbolTable, lastRuntimeVisibleAnnotation, lastRuntimeInvisibleAnnotation, lastRuntimeVisibleTypeAnnotation, lastRuntimeInvisibleTypeAnnotation, result);
		symbolTable.putBootstrapMethods(result);
		if (moduleWriter != null) {
			moduleWriter.putAttributes(result);
		}
		if (nestHostClassIndex != 0) {
			result.putShort(symbolTable.addConstantUtf8(Constants.NEST_HOST)).putInt(2).putShort(nestHostClassIndex);
		}
		if (nestMemberClasses != null) {
			result.putShort(symbolTable.addConstantUtf8(Constants.NEST_MEMBERS)).putInt(nestMemberClasses.length + 2).putShort(numberOfNestMemberClasses).putByteArray(nestMemberClasses.data, 0, nestMemberClasses.length);
		}
		if (firstAttribute != null) {
			firstAttribute.putAttributes(symbolTable, result);
		}

		// Third step: replace the ASM specific instructions, if any.
		if (hasAsmInstructions) {
			return replaceAsmInstructions(result.data, hasFrames);
		} else {
			return result.data;
		}
	}

	/**
	 * Returns the equivalent of the given class file, with the ASM specific
	 * instructions replaced with standard ones. This is done with a ClassReader
	 * -&gt; ClassWriter round trip.
	 *
	 * @param classFile a class file containing ASM specific instructions,
	 *            generated by this ClassWriter.
	 * @param hasFrames whether there is at least one stack map frames in
	 *            'classFile'.
	 * @return an equivalent of 'classFile', with the ASM specific instructions
	 *         replaced with standard ones.
	 */
	private byte[] replaceAsmInstructions(final byte[] classFile, final boolean hasFrames) {
		final Attribute[] attributes = getAttributePrototypes();
		firstField = null;
		lastField = null;
		firstMethod = null;
		lastMethod = null;
		lastRuntimeVisibleAnnotation = null;
		lastRuntimeInvisibleAnnotation = null;
		lastRuntimeVisibleTypeAnnotation = null;
		lastRuntimeInvisibleTypeAnnotation = null;
		moduleWriter = null;
		nestHostClassIndex = 0;
		numberOfNestMemberClasses = 0;
		nestMemberClasses = null;
		firstAttribute = null;
		compute = hasFrames ? MethodWriter.COMPUTE_INSERTED_FRAMES : MethodWriter.COMPUTE_NOTHING;
		new ClassReader(classFile, 0, /* checkClassVersion = */ false).accept(this, attributes, (hasFrames ? ClassReader.EXPAND_FRAMES : 0) | ClassReader.EXPAND_ASM_INSNS);
		return toByteArray();
	}

	/**
	 * Returns the prototypes of the attributes used by this class, its fields
	 * and its methods.
	 *
	 * @return the prototypes of the attributes used by this class, its fields
	 *         and its methods.
	 */
	private Attribute[] getAttributePrototypes() {
		Attribute.Set attributePrototypes = new Attribute.Set();
		attributePrototypes.addAttributes(firstAttribute);
		FieldWriter fieldWriter = firstField;
		while (fieldWriter != null) {
			fieldWriter.collectAttributePrototypes(attributePrototypes);
			fieldWriter = (FieldWriter) fieldWriter.fv;
		}
		MethodWriter methodWriter = firstMethod;
		while (methodWriter != null) {
			methodWriter.collectAttributePrototypes(attributePrototypes);
			methodWriter = (MethodWriter) methodWriter.mv;
		}
		return attributePrototypes.toArray();
	}

	// -----------------------------------------------------------------------------------------------
	// Utility methods: constant pool management for Attribute sub classes
	// -----------------------------------------------------------------------------------------------

	/**
	 * Adds a number or string constant to the constant pool of the class being
	 * build. Does nothing if the constant pool already contains a similar item.
	 * <i>This method is intended for {@link Attribute} sub classes, and is
	 * normally not needed by class generators or adapters.</i>
	 *
	 * @param value the value of the constant to be added to the constant pool.
	 *            This parameter must be an {@link Integer}, a {@link Float}, a
	 *            {@link Long}, a {@link Double} or a {@link String}.
	 * @return the index of a new or already existing constant item with the
	 *         given value.
	 */
	public int newConst(final Object value) {
		return symbolTable.addConstant(value).index;
	}

	/**
	 * Adds an UTF8 string to the constant pool of the class being build. Does
	 * nothing if the constant pool already contains a similar item. <i>This
	 * method is intended for {@link Attribute} sub classes, and is normally not
	 * needed by class generators or adapters.</i>
	 *
	 * @param value the String value.
	 * @return the index of a new or already existing UTF8 item.
	 */
	// DontCheck(AbbreviationAsWordInName): can't be renamed (for backward
	// binary compatibility).
	public int newUTF8(final String value) {
		return symbolTable.addConstantUtf8(value);
	}

	/**
	 * Adds a class reference to the constant pool of the class being build.
	 * Does nothing if the constant pool already contains a similar item.
	 * <i>This method is intended for {@link Attribute} sub classes, and is
	 * normally not needed by class generators or adapters.</i>
	 *
	 * @param value the internal name of the class.
	 * @return the index of a new or already existing class reference item.
	 */
	public int newClass(final String value) {
		return symbolTable.addConstantClass(value).index;
	}

	/**
	 * Adds a method type reference to the constant pool of the class being
	 * build. Does nothing if the constant pool already contains a similar item.
	 * <i>This method is intended for {@link Attribute} sub classes, and is
	 * normally not needed by class generators or adapters.</i>
	 *
	 * @param methodDescriptor method descriptor of the method type.
	 * @return the index of a new or already existing method type reference
	 *         item.
	 */
	public int newMethodType(final String methodDescriptor) {
		return symbolTable.addConstantMethodType(methodDescriptor).index;
	}

	/**
	 * Adds a module reference to the constant pool of the class being build.
	 * Does nothing if the constant pool already contains a similar item.
	 * <i>This method is intended for {@link Attribute} sub classes, and is
	 * normally not needed by class generators or adapters.</i>
	 *
	 * @param moduleName name of the module.
	 * @return the index of a new or already existing module reference item.
	 */
	public int newModule(final String moduleName) {
		return symbolTable.addConstantModule(moduleName).index;
	}

	/**
	 * Adds a package reference to the constant pool of the class being build.
	 * Does nothing if the constant pool already contains a similar item.
	 * <i>This method is intended for {@link Attribute} sub classes, and is
	 * normally not needed by class generators or adapters.</i>
	 *
	 * @param packageName name of the package in its internal form.
	 * @return the index of a new or already existing module reference item.
	 */
	public int newPackage(final String packageName) {
		return symbolTable.addConstantPackage(packageName).index;
	}

	/**
	 * Adds a handle to the constant pool of the class being build. Does nothing
	 * if the constant pool already contains a similar item. <i>This method is
	 * intended for {@link Attribute} sub classes, and is normally not needed by
	 * class generators or adapters.</i>
	 *
	 * @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD},
	 *            {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
	 *            {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL},
	 *            {@link Opcodes#H_INVOKESTATIC},
	 *            {@link Opcodes#H_INVOKESPECIAL},
	 *            {@link Opcodes#H_NEWINVOKESPECIAL} or
	 *            {@link Opcodes#H_INVOKEINTERFACE}.
	 * @param owner the internal name of the field or method owner class.
	 * @param name the name of the field or method.
	 * @param descriptor the descriptor of the field or method.
	 * @return the index of a new or already existing method type reference
	 *         item.
	 * @deprecated this method is superseded by
	 *             {@link #newHandle(int, String, String, String, boolean)}.
	 */
	@Deprecated
	public int newHandle(final int tag, final String owner, final String name, final String descriptor) {
		return newHandle(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE);
	}

	/**
	 * Adds a handle to the constant pool of the class being build. Does nothing
	 * if the constant pool already contains a similar item. <i>This method is
	 * intended for {@link Attribute} sub classes, and is normally not needed by
	 * class generators or adapters.</i>
	 *
	 * @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD},
	 *            {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
	 *            {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL},
	 *            {@link Opcodes#H_INVOKESTATIC},
	 *            {@link Opcodes#H_INVOKESPECIAL},
	 *            {@link Opcodes#H_NEWINVOKESPECIAL} or
	 *            {@link Opcodes#H_INVOKEINTERFACE}.
	 * @param owner the internal name of the field or method owner class.
	 * @param name the name of the field or method.
	 * @param descriptor the descriptor of the field or method.
	 * @param isInterface true if the owner is an interface.
	 * @return the index of a new or already existing method type reference
	 *         item.
	 */
	public int newHandle(final int tag, final String owner, final String name, final String descriptor, final boolean isInterface) {
		return symbolTable.addConstantMethodHandle(tag, owner, name, descriptor, isInterface).index;
	}

	/**
	 * Adds a dynamic constant reference to the constant pool of the class being
	 * build. Does nothing if the constant pool already contains a similar item.
	 * <i>This method is intended for {@link Attribute} sub classes, and is
	 * normally not needed by class generators or adapters.</i>
	 *
	 * @param name name of the invoked method.
	 * @param descriptor field descriptor of the constant type.
	 * @param bootstrapMethodHandle the bootstrap method.
	 * @param bootstrapMethodArguments the bootstrap method constant arguments.
	 * @return the index of a new or already existing dynamic constant reference
	 *         item.
	 */
	public int newConstantDynamic(final String name, final String descriptor, final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments) {
		return symbolTable.addConstantDynamic(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments).index;
	}

	/**
	 * Adds an invokedynamic reference to the constant pool of the class being
	 * build. Does nothing if the constant pool already contains a similar item.
	 * <i>This method is intended for {@link Attribute} sub classes, and is
	 * normally not needed by class generators or adapters.</i>
	 *
	 * @param name name of the invoked method.
	 * @param descriptor descriptor of the invoke method.
	 * @param bootstrapMethodHandle the bootstrap method.
	 * @param bootstrapMethodArguments the bootstrap method constant arguments.
	 * @return the index of a new or already existing invokedynamic reference
	 *         item.
	 */
	public int newInvokeDynamic(final String name, final String descriptor, final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments) {
		return symbolTable.addConstantInvokeDynamic(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments).index;
	}

	/**
	 * Adds a field reference to the constant pool of the class being build.
	 * Does nothing if the constant pool already contains a similar item.
	 * <i>This method is intended for {@link Attribute} sub classes, and is
	 * normally not needed by class generators or adapters.</i>
	 *
	 * @param owner the internal name of the field's owner class.
	 * @param name the field's name.
	 * @param descriptor the field's descriptor.
	 * @return the index of a new or already existing field reference item.
	 */
	public int newField(final String owner, final String name, final String descriptor) {
		return symbolTable.addConstantFieldref(owner, name, descriptor).index;
	}

	/**
	 * Adds a method reference to the constant pool of the class being build.
	 * Does nothing if the constant pool already contains a similar item.
	 * <i>This method is intended for {@link Attribute} sub classes, and is
	 * normally not needed by class generators or adapters.</i>
	 *
	 * @param owner the internal name of the method's owner class.
	 * @param name the method's name.
	 * @param descriptor the method's descriptor.
	 * @param isInterface {@literal true} if {@code owner} is an interface.
	 * @return the index of a new or already existing method reference item.
	 */
	public int newMethod(final String owner, final String name, final String descriptor, final boolean isInterface) {
		return symbolTable.addConstantMethodref(owner, name, descriptor, isInterface).index;
	}

	/**
	 * Adds a name and type to the constant pool of the class being build. Does
	 * nothing if the constant pool already contains a similar item. <i>This
	 * method is intended for {@link Attribute} sub classes, and is normally not
	 * needed by class generators or adapters.</i>
	 *
	 * @param name a name.
	 * @param descriptor a type descriptor.
	 * @return the index of a new or already existing name and type item.
	 */
	public int newNameType(final String name, final String descriptor) {
		return symbolTable.addConstantNameAndType(name, descriptor);
	}

	// -----------------------------------------------------------------------------------------------
	// Default method to compute common super classes when computing stack map
	// frames
	// -----------------------------------------------------------------------------------------------

	/**
	 * Returns the common super type of the two given types. The default
	 * implementation of this method <i>loads</i> the two given classes and uses
	 * the java.lang.Class methods to find the common super class. It can be
	 * overridden to compute this common super type in other ways, in particular
	 * without actually loading any class, or to take into account the class
	 * that is currently being generated by this ClassWriter, which can of
	 * course not be loaded since it is under construction.
	 *
	 * @param type1 the internal name of a class.
	 * @param type2 the internal name of another class.
	 * @return the internal name of the common super class of the two given
	 *         classes.
	 */
	protected String getCommonSuperClass(final String type1, final String type2) {
		ClassLoader classLoader = getClassLoader();
		Class<?> class1;
		try {
			class1 = Class.forName(type1.replace('/', '.'), false, classLoader);
		} catch (ClassNotFoundException e) {
			throw new TypeNotPresentException(type1, e);
		}
		Class<?> class2;
		try {
			class2 = Class.forName(type2.replace('/', '.'), false, classLoader);
		} catch (ClassNotFoundException e) {
			throw new TypeNotPresentException(type2, e);
		}
		if (class1.isAssignableFrom(class2)) {
			return type1;
		}
		if (class2.isAssignableFrom(class1)) {
			return type2;
		}
		if (class1.isInterface() || class2.isInterface()) {
			return "java/lang/Object";
		} else {
			do {
				class1 = class1.getSuperclass();
			} while (!class1.isAssignableFrom(class2));
			return class1.getName().replace('.', '/');
		}
	}

	/**
	 * Returns the {@link ClassLoader} to be used by the default implementation
	 * of {@link #getCommonSuperClass(String, String)}, that of this
	 * {@link ClassWriter}'s runtime type by default.
	 *
	 * @return ClassLoader
	 */
	protected ClassLoader getClassLoader() {
		return getClass().getClassLoader();
	}
}
